﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Media;
using System.Windows.Shapes;
using eProject.Model;
using eProject.ViewModel;


namespace eProject.View
{

    public class TimeGrid : Canvas, INotifyPropertyChanged
    {

        private const double OpacityConst = 0.5;

        #region Constructor

        public TimeGrid()
        {
            var hline1 = new Line { X1 = 0, Y1 = ViewConstants.TimeGridLabelPadding, Y2 = ViewConstants.TimeGridLabelPadding, StrokeThickness = 1, Stroke = Brushes.Black, Opacity = OpacityConst };
            var binding1 = new Binding("ActualWidth") { Source = this };
            hline1.SetBinding(Line.X2Property, binding1);
            Children.Add(hline1);

            var hline2 = new Line { X1 = 0, Y1 = ViewConstants.TimeGridLabelDayTopPadding, Y2 = ViewConstants.TimeGridLabelDayTopPadding, StrokeThickness = 1, Stroke = Brushes.Black, Opacity = OpacityConst };
            var binding2 = new Binding("ActualWidth") { Source = this };
            hline2.SetBinding(Line.X2Property, binding2);
            Children.Add(hline2);
         
            SizeChanged += OnSizeChanged;

            var dpdcm = DependencyPropertyDescriptor.FromProperty(CommonManagerProperty, typeof(TimeGrid));
            dpdcm.AddValueChanged(this, delegate { RefreshManager(); });

            var dpdce = DependencyPropertyDescriptor.FromProperty(CurrentEmployeeProperty, typeof(TimeGrid));
            dpdce.AddValueChanged(this,delegate{RedrawDayOff();});

            
        }

        #endregion

        #region Methods

        private void RefreshPeriod()
        {
        	if (CommonManager == null) return;
        	var widthInDays = ((int) (Math.Ceiling(ActualWidth/ViewConstants.PixelsPerDay)));
            var period = widthInDays > CommonManager.Period.Duration ?
                new Period(CommonManager.Period.Begin, widthInDays) : CommonManager.Period;           
            if (!Period.Equals(period))
            {
            	SetPeriod(period);
            }
        }
        
        private void OnSizeChanged(object sender, SizeChangedEventArgs e)
        {
        	RefreshPeriod();
        }

        private void OnPeriodChanged(Period oldOne, Period newOne)
        {
        	RefreshPeriod();
        }

        private void SetPeriod(Period period)
        {
            var oldPeriod = Period;
            Period = period;

            var maxPosition = period.Duration*ViewConstants.PixelsPerDay;

            var labelsToDelete = _labels.Where(l => GetLeft(l) < 0 || GetLeft(l) >= maxPosition).ToList();
            var linesToDelete = _lines.Where(l => l.X1 < 0 || l.X1 >= maxPosition).ToList();
            var monthLabelsToDelete = _monthLabels.Where(g => GetLeft(g.Value) < 0 || GetLeft(g.Value) >= maxPosition).ToList();
            var dayOffrectsToDelete = _dayOffRectangles.Where(r => GetLeft(r.Value) < 0 || GetLeft(r.Value) >= maxPosition).ToList();

            foreach (var label in labelsToDelete)
            {
                _labels.Remove(label);
                Children.Remove(label);
                BindingOperations.ClearAllBindings(label);
            }
                       
            foreach (var line in linesToDelete)
            {                
                _lines.Remove(line);
                Children.Remove(line);
                BindingOperations.ClearAllBindings(line);
            }

            foreach (var monthLabel in monthLabelsToDelete)
            {
                _monthLabels.Remove(monthLabel.Key);
                Children.Remove(monthLabel.Value);
                BindingOperations.ClearAllBindings(monthLabel.Value);
            }

            foreach (var rect in dayOffrectsToDelete)
            {
                Children.Remove(rect.Value);
                _dayOffRectangles.Remove(rect);
                BindingOperations.ClearAllBindings(rect.Value);
            }

            var addToLeft = (int) (oldPeriod.Begin - Period.Begin).TotalDays;
            var addToRight = (int) (Period.End - oldPeriod.End).TotalDays;

            if (addToLeft > 0) CreateLabels(Period.Begin, addToLeft);
            if (addToRight > 0) CreateLabels(oldPeriod.End, addToRight);
        }                

        private void RefreshManager()
        {
            if (_manager != null)
            {
                _manager.PeriodChanged -= OnPeriodChanged;
                _manager.ScheduleChanged -= OnScheduleChanged;
            }
            _manager = CommonManager;
            ////TODO changing control
            //if (_labels.Count() != 0)
            //{
            //    foreach (var label in _labels)
            //    {
            //        Children.Remove(label);
            //    }
            //    _labels.Clear();
            //}
            Period = CommonManager.Period;
            CreateLabels(Period.Begin, (int)Period.Duration);
            CommonManager.PeriodChanged += OnPeriodChanged;
            CommonManager.ScheduleChanged += OnScheduleChanged;
           
        }

        private void OnScheduleChanged()
        {
            RedrawDayOff();
        }

        private void CreateLabels(DateTime begin, int count)
        {
            var dt = begin;
            for (var i = 0; i < count; i++)
            {
                var text = new TextBlock { Text = dt.Day.ToString(), Opacity = OpacityConst };

                var lbbind = new Binding("Period")
                                 {Source = this, Converter = LabelLeftConverter.Instance, ConverterParameter = dt};
                text.SetBinding(LeftProperty, lbbind);
                SetTop(text, ViewConstants.TimeGridLabelDayTopPadding);
                _labels.Add(text);
                Children.Add(text);

                var lineY = dt.Day == 1 ? 0 : ViewConstants.TimeGridLabelDayTopPadding;
                var thickness = dt.Day == 1 ? 1.0 : 0.5;
                var line = new Line { Y1 = lineY, StrokeThickness = thickness, Stroke = Brushes.Black, Opacity = OpacityConst };
                line.SetBinding(Line.X1Property, lbbind);
                line.SetBinding(Line.X2Property, lbbind);
                var bind = new Binding("ActualHeight") { Source = this };
                line.SetBinding(Line.Y2Property, bind);
                _lines.Add(line);
                Children.Add(line);

                var firstDay = new DateTime(dt.Year, dt.Month, 1);
                if ((dt.Day == 1 || dt == _period.Begin)&&!_monthLabels.ContainsKey(firstDay))
                {
                    
                    
                    var monthLabel = new TextBlock
                    {
                        Text = dt.Date.ToString("MMMM"),
                        TextAlignment = TextAlignment.Center,    
                        Opacity = OpacityConst 
                        
                    };
                    SetTop(monthLabel, 0);

                    var gleftbind = new Binding("Period")
                                        {
                                            Source = this,
                                            Converter = MonthLeftConverter.Instance,
                                            ConverterParameter = dt.Date,
                                        };
                    monthLabel.SetBinding(LeftProperty, gleftbind);
                    var gwidthbind = new Binding("Period")
                                         {
                                             Source = this,
                                             Converter = MonthWidthConverter.Instance,
                                             ConverterParameter = dt.Date
                                         };
                    monthLabel.SetBinding(WidthProperty, gwidthbind);
                    Children.Add(monthLabel);
                    _monthLabels.Add(firstDay, monthLabel);
                }
                RedrawDayOff(dt);

                dt = dt.AddDays(1);
            }
            if (Period.End < dt) Period = new Period(Period.Begin, dt);
        }

        private void AddDayOff(DateTime dt)
        {
            var lbbind = new Binding("Period") { Source = this, Converter = LabelLeftConverter.Instance, ConverterParameter = dt };
            var bind = new Binding("ActualHeight") { Source = this };
            var rect = new Rectangle
            {
                Fill = Brushes.Red,
                Opacity = 0.07,
                Width = ViewConstants.PixelsPerDay
            };
            SetTop(rect, ViewConstants.TimeGridLabelDayTopPadding);
            rect.SetBinding(LeftProperty, lbbind);
            rect.SetBinding(HeightProperty, bind);
            Children.Add(rect);
            _dayOffRectangles.Add(dt,rect);
        }

        private void RedrawDayOff()
        {
            if (Period == null) return;
            var dt = Period.Begin;
            var end = Period.End;            
     
            while (dt < end)
            {
                RedrawDayOff(dt);
                dt = dt.AddDays(1);
            }
        }

        private void RedrawDayOff(DateTime dt)
        {
            var isAlreadyDayOff = _dayOffRectangles.Keys.Contains(dt);
            if (CommonManager.IsDayOff(dt, CurrentEmployee))
            {
                if (!isAlreadyDayOff) AddDayOff(dt);
            }
            else if (isAlreadyDayOff)
            {
                Children.Remove(_dayOffRectangles[dt]);
                _dayOffRectangles.Remove(dt);
            }
        }
       

        private void InvokePropertyChanged(PropertyChangedEventArgs e)
        {
            var changed = PropertyChanged;
            if (changed != null) changed(this, e);
        }

        #endregion

        #region Properties

        public static readonly DependencyProperty CurrentEmployeeProperty = DependencyProperty.Register("CurrentEmployee",typeof(EmployeeViewModel), typeof(TimeGrid));

        public static readonly DependencyProperty CommonManagerProperty =
            DependencyProperty.RegisterAttached("CommonManager", typeof (CommonManager), typeof (TimeGrid));

        public CommonManager CommonManager
        {
            get { return (CommonManager)GetValue(CommonManagerProperty); }
            set { SetValue(CommonManagerProperty, value); }
        }

        public EmployeeViewModel CurrentEmployee
        {
            get { return (EmployeeViewModel)GetValue(CurrentEmployeeProperty); }
            set
            {
                SetValue(CurrentEmployeeProperty,value);               
            }
        }

        public Period Period 
        { 
            get { return _period; } 
            private set
            {
            	if (_period != null && _period.Equals(value)) return;
                _period = value;
                InvokePropertyChanged(new PropertyChangedEventArgs("Period"));

            } 
        }
         

        #endregion

        #region Fields

        private readonly ICollection<TextBlock> _labels = new LinkedList<TextBlock>();
        private readonly ICollection<Line> _lines = new LinkedList<Line>();
        private readonly IDictionary<DateTime,Rectangle> _dayOffRectangles = new Dictionary<DateTime, Rectangle>();
        private readonly IDictionary<DateTime, TextBlock> _monthLabels = new Dictionary<DateTime, TextBlock>();
        private Period _period;

        private CommonManager _manager;        

        #endregion

        #region Events

        public event PropertyChangedEventHandler PropertyChanged;

        #endregion

        #region Converters

        public class LabelLeftConverter : IValueConverter
        {
            private LabelLeftConverter(){}

            public static readonly LabelLeftConverter Instance = new LabelLeftConverter();

            public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
            {
                var period = value as Period;
                if (period == null) throw new Exception("Incorrect data type");

                var param = (DateTime)parameter;

                var result = (param - period.Begin).TotalDays * ViewConstants.PixelsPerDay;
                return result;
            }

            public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
            {
                return null;
            }
        }

        public class MonthLeftConverter : IValueConverter
        {

            public static readonly MonthLeftConverter Instance = new MonthLeftConverter();
            private MonthLeftConverter(){}


            public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
            {
                var period = value as Period;
                if (period == null) throw new Exception("Incorrect data type");

                var param = (DateTime)parameter;
                
                if (param.Day != 1) param = new DateTime(param.Year, param.Month, 1);

                var begin = param < period.Begin && param.Month == period.Begin.Month && param.Year == period.Begin.Year
                                ? period.Begin
                                : param;          
      
                var result = (begin - period.Begin).TotalDays * ViewConstants.PixelsPerDay;

                return result;
            }

            public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
            {
                throw new NotImplementedException();
            }
        }

        public class MonthWidthConverter : IValueConverter
        {
            public static readonly MonthWidthConverter Instance = new MonthWidthConverter();
            private MonthWidthConverter(){}

            public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
            {
                var period = value as Period;
                if (period == null) throw new Exception("Incorrect data type");

                var param = (DateTime)parameter;

                if (param.Day != 1) param = new DateTime(param.Year, param.Month, 1);

                var begin = param < period.Begin ? period.Begin : param;
                var lastday = new DateTime(param.Year, param.Month, DateTime.DaysInMonth(param.Year, param.Month));
                var end = lastday > period.End ? period.End : lastday;

                var result = (int) (end - begin).TotalDays * ViewConstants.PixelsPerDay;
                return result;

            }

            public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
            {
                throw new NotImplementedException();
            }
        }

        #endregion
    }
}
